//
//  main.m
//  Classes & Objects
//
//  Created by admin on 2019/7/29.
//  Copyright © 2019 猫爷爷的酱醋茶. All rights reserved.
//

/*
 
 先有对象还是先有类？ 类：具有相同特征和行为的事物。
 从现实的角度，是先有对象再有对对象的提取归类
 从代码的角度，是先有类才能创建出对象。
 
 类的本质是自定义的一个数据类型，
 属性的本质是变量。
 
 1.定义类的语法：
    a。 位置： 直接在源文件之中定义，而不是在mian函数内定义
    b。 类的定义分为两部分
        -> 类的声明
        @interface 类名: NSObject
         {
            这类事物具有的共同的特征，将他们定义为变量。// 就是属性/成员变量/实例变量/字段...
            属性名用_开头
         }
        功能其实就是一个方法，将方法声明在这里。
        @end
 
        -> 类的实现
        @implementation 类名
        将方法的实现写在这里。
        @end
 
    提示：
        方法就是一个功能，C语言中使用函数表示一个功能，
        OC里面的类的具体行为用方法来表示。
 
    注意：函数和方法的区别
        a。函数可以直接调用，方法必须要先创建对象，通过对象来调用。方法是属于对象的。
        b。函数即使声明在类的内部，也不属于类。
 
 
    1）无参数的方法
       a. 声明：
                - (返回值的类型) 方法名;
             例：- (void) run;
        b.方法的调用
            [对象名 方法名];
 
    2）有参数的方法
        a. 声明：
                -(返回值的类型) 方法名:(参数类型)参数名;
             例：-(void)eat:(NSString *)foodName;
                定义了1个方法：没有返回值
                方法名称叫做eat:
                方法有一个参数，类型是NSString *，参数的名字叫做foodName
         b.方法的调用
            [对象名 方法名:参数];
 
     3）有多个参数的方法
        a. 声明：
                -(返回值的类型) 方法名:(参数类型)参数名1 :(参数类型)参数名2 :(参数类型)参数名3;
             例：-(int)sum:(int)num1 :(int)num2;
                定义了1个方法：返回值为int类型
                方法名称叫做sum: :
                方法有两个参数，类型是int，参数的名字叫做num1,num2
        b.方法的调用
            [对象名 方法名:参数1 :参数2 :参数3];
 
    注意：
        1）如果方法中只有一个参数，要求最好把这个方法定义为xxxWith:
        2）如果方法中有多个参数，最好把方法定义为
            (方法名)With: and(参数类型):参数名1 :and(参数类型):参数名1
        3）在方法的实现中，可以直接访问属性
            例：Student里的sum方法
 
 
2. 如何访问对象的属性：
    1）默认情况下，对象的属性不允许被外界直接访问的
        如果允许对象的属性可以被外界访问，那么声明属性的地方用@public修饰
    2）访问对象的属性
        第一种方式：（常用）
            a。赋值
            对象名->属性名 = 值;
            b。取值
            对象名->属性名;
        第二种方式：
            a。赋值
            (*对象名).属性名 = 值;
            b。取值
            (*对象名).属性名;
 
 3. 类在内存中的存取
 
    类加载：
    在程序运行期间，当某个类第一次被访问的时候，会将这个类存储到内存中的代码段区域中。
 
 4. 对象在内存中的存储
 
    例： Person *p1 = [Person new];
 
    1）Person *p1; 会在栈内存中申请一个Person类型的指针变量p1，p1是个指针变量，只能存储地址
    2）[Person new]; 真正在内存中创建对象的代码。new做的事情：
        a。在堆内存中申请一块合适大小的空间
        b。在这个空间中，根据类的模板来创建对象，声明对应的属性。
            对象中还存在另外一个属性，叫做isa，是个指针，指向对象所属的类，存储在代码段的地址。
        c。初始化对象的类型。
            如果属性的类型是基本的数据类型，那么就赋值为0；
            如果属性的类型是C语言的指针类型，那么就赋值为NULL；
            如果属性的类型是OC的类指针类型，那么k就赋值为nil；
            C语言的NULL表示空指针，nil表示OC语言的空对象
        d。返回对象的地址
    3）对象只有属性，没有方法。
 
    注意：1）如何访问对象属性
            指针名->属性名 例： p1->_name
            根据指针找到指针所指向的对象，在找到对象中的属性来访问
 
        2）如何调用方法
            [指针名 方法名];  例：[p1 run];
            向通过指针找到指针所指向的对象，对象发行调用方法，再通过调用isa指针地址找到Person类，再调用类里面的方法
 
 5. 类
     的本质是自定义的一个数据类型，所以可以作为方法的参数。
 
 例：-(void)test:(Dog *)dog;
 
    1.当对象作为方法的参数的时候，参数类型是类指针。
    2.给实参的时候，也必须是符合参数的对象。
 
 
 如果要在方法中调用当前对象的另外一个方法，使用
    [self 方法名];
 
6. 对象方法和类方法
 
 对象方法：  需先创建对象，通过对象名调用 [对象名 方法名]; 例 [p1 run];
    -(返回值类型)方法名;
 
 类方法：    直接通过类名[类名 类方法名];
    +(返回值类型)方法名;
 
    特点：
        节约空间：因为调用类方法不需要创建对象
        提高效率：内存直接找到类的方法执行
        类方法中不能直接访问属性、对象方法，
 
    因为对象方法、属性，是随着对象的创建一起创建在对象之中，
    类被第一次访问的时候，会做类加载，也就是将代码拷贝到内存中的代码段中，但是这个时候对象还没有被创建，所以不能访问属性。
    但是，可以通过在类方法中创建一个对象，访问这个对象的属性和方法。

    而在对象方法可以直接调用类方法。
 
 7. 匿名对象
        没有名字的对象，如果我们创建一个对象，没有用一个指针存储这个对象的地址，也就是没有任何指针指向这个对象
 
    [Person new]; 实际上是一个Person的类方法，返回值为创建对象的地址，所以可以直接使用这个地址
    例： [Person new]->_name = @"猫爷爷的酱醋茶";
        [[Person new] sayHi];  //这相当于又创建了一次匿名对象，再调用sayHi方法。
 
 注意：匿名对象只能使用一次，每次创建的匿名对象都是不同的对象。
        所以，如果某个对象只是用一次，那么久可以使用匿名对象。
 
 
 8. 关系
 
 组合关系
    多个类对象组合成一个新的对象，那么它们之间的关系就是组合关系。
 
 依赖关系
    一个对象的方法参数是另外一个
 
 关联关系
    一个类作为另外一个类的属性，也叫做拥有关系。
 
 
 9. 耦合度：
        表述当修改一个对象的时候，对另外一个对象的影响程度。
 
 低耦合：
 当修改一个对象的时候，对另外一个对象的影响很小甚至没有影响。
 
 高内聚：
 一个对象仅仅只做自己相关的事情。
 
 
 //如果方法的对象的返回值是当前类的对象，那么方法的返回值就写instancetype;
 
 
10. 访问修饰符
        只能用来修饰属性，可以限定对象的属性的访问范围。不能用来修饰方法。
 
 a）@private 私有。
        被修饰的属性，只能在本类的内部访问，只能在本类的方法实现中访问。
        子类仍然继承有父类的私有属性，但是在子类中无法直接访问，如果父类中有方法为属性赋值或者取值，
     那么可以子类可以通过方法简洁访问父类的私有属性。
 
        但是即使使用@private修饰，但在Xcode下依然会有提示，只是无权访问，如何实现真私有呢？
        其实在@implementation之中也可以写一个大括弧，把类的属性定义在此，就为私有属性，其他修饰符
    对其没有效果，且没有提示。
 
 b）@protected 受保护的。
        被修饰的属性，只能在本类和本类的子类的方法中访问。//默认状态
 c）@package
        被修饰的属性，可以在当前框架中访问。
 d）@public 公共的。
        被修饰的属性，可以在任意的地方访问。
 
11. 私有方法
    一个类中，方法不写声明，只有实现，那么这个方法就是私有方法，外界无法访问，但是内部可以调用。
 
12. 里氏替换原则 - LSP
    Person *p1 = [Student new];
    子类可以替换父类的位置，并且程序的功能不受影响。
    当一个父类的指针指向的是一个子类对象的时候，这里就有里氏替换原则。
 
 注意：1）但是，通过这个指针只能通过子类调用父类的成员，子类独有的成员无法访问。
      2）通过这个父类指针调用的方法，如果在子类中重写了，调用的就是子类重写的方法。
 
  作用：
    1）一个指针中，不仅可以存储本类对象的地址，还可以存储子类对象的地址。
    2）如果一个指针的类型是NSObject类型的，那么这个指针中可以存储任意的OC对象的地址
 
 
 13. 重写方法
    只需要在方法的实现再写一遍。这也是形成类多态的方式。
 
 14. description - NSObjc类中的对象
    如果希望使用%@打印一个对象的时候，输出的是自定义的内容，那么就可以重写这个方法。
 
 15. 继承的特点： 单根性、传递性。
 
 16. class类对象
        类是以class对象的形式存储在代码段。
 
    如何拿到存储在代码段中的类对象？
    a）调用类方法class 就可以得到存储类的对象地址
        Class c1 = [Person class];   //定义c1不用加*，因为在typedef已经定为*...
    b）调用对象方法class 就可以得到存储这个对象所属的Class对象的地址
        Person *p1 = [Person new];
        Class c2 = [p1 class];
    c）对象中的isa指针的值，其实就是代码段中存储对象的类对象的地址
 
 17. SEL类对象
        SEL其实是一个类，SEL对象用来存储类的一个方法。
        1）先创建一个SEL对象
        2）将方法的信息存储在这个SEL对象之中
        3）再将这个SEL作为class对象的一个属性
 
   1） 如何拿到存储方法的SEL对象？
        a）因为SEL是一个typedef类型的，在自定义的时候已经加了*
           所以在声明SEL指针的时候，不需要加*
        b）具体取SEL对象的语法
            SEL s1 = @selector(方法名);
 
   2） 调用方法的本质：就是为对象发送SEL消息
        [p1 sayHi];
        内部的原理：
        1）先拿到存储sayHi方法的SEL对象，也就是拿到存储sayHi方法的SEL数据，也叫做SEL消息
        2）将这个SEL消息发送给p1对象，p1对象接收到SEL消息，知道要调用方法
        3）根据对象的isa指针查找存储的类对象
        4）找到类对象之后，在类对象中查找是否有传入的SEL数据相匹配
           如果有就执行，如果没有再找父类，直到NSObject
 
    3）手动发送SEL消息
        a）先拿到sayHi方法的SEL数据
            Person *p1 = [Person new];
            SEL s1 = @selector(sayHi);
        b）将这个SEL消息发送给p1对象
            [p1 performSelector:s1];
        c）如果有参数
            [p1 performSelector:方法名 withObject:参数];
        d）如果参数很多 可以通过传入一个类，类中的对象可以是这些参数
 
 18. 点语法的原理
    点语法在编译的时候，其实会将点语法转换为settet和getter方法。
 
    注意：
    a）在getter和setter方法里，慎用点语法，容易造成无限递归的死循环，导致程序崩溃。
    b）如果我们的setter和getter方法名不规范，那么点语法无法使用
    c）如果属性没有封装getter和setter方法，那么无法使用点语法访问。
 
 19.category 分类/类目
    作用：
        a）分成多个模块，把一个类分为多个模块。
        b）为一个已经存在的类添加方法。  用继承也能实现这个功能。
 
            继承：新创建一个类
                 继承的扩展，可以扩展任何成员
            分类：没有新创建一个类，看起来就像修改的是本类一样
                 分类只能添加方法。
 
    注意：
        a）分类只能增加方法，不能增加属性
        b）在分类中，可以写@property 但不会自动生成私有属性，也不会自动生成setter、getter实现。
            只会生成setter和getter的声明
        c）在分类的方法实现中，不可以直接访问本类的真私有属性，（定义在@implementation之中）
           但是可以通过点语法调用getter和setter方法来访问属性。
        d）分类中可以存在和本类同名方法，调用时优先调用分类的方法，哪怕没有引入分类的头文件。
           如果多个分类中有相同的方法，优先调用最后编译的分类方法。
 
 20. 非正式协议
     为系统自带的类写分类，这个就叫做非正式协议。
 
 21. 延展 Extension ：天生用来私有化类的成员的。
     1）是一个特殊的分类，所以延展是类的一部分
     2）特殊之处：
        a. 延展这个特殊的分类没有名字；
        b. 只有声明没有实现，和本类共享一个实现。
        c. 不单独占用一个文件，而是将延展直接写在本类的实现中。
            这样，写在延展的成员中，就相当于是这个类的私有成员，只能在内部使用，外部不能访问。
    语法：
    @interface 本类名 ()
     //...
    @end
 
    没有实现，和本类共享一个实现。
 
  和分类的区别
    1）分类有名字，延展没有名字，延展可以说是一个匿名分类。
    2）每一个分类都有单独的声明和实现，而延展只是声明，有实现（比如@property自动生成setter和getter实现）但没有单独的实现
    3）分类只能新增方法，而延展中任意的成员（属性和方法）都可以写
    4）分类中可以写@property 但是只会生成setter和getter方法的声明
       而延展中@property会自动生成私有属性 也会生成setter和getter
 
 延展的应用场景：
    a）如果想要为类写一个真私有属性，虽然我们可以定义在@implementation之中，但不要这么写，这样很不规范
       而是写一个延展，将这个属性定义在延展中。
 
    b）为类写一个私有的@property - 生成setter和getter方法只能在内部使用，外部不能访问。既有实现，但没有声明。
       这个时候使用延展，把成员写在本类的实现中。例：Student.m
 
    c）如果要为类写一个私有方法，虽然可以直接在本类中直接实现而不声明方法，但是建议将声明写在延展中，
      实现写在本类的实现中，可以提高阅读性。
 
 alloc 方法
 作用：创建对象并返回
    本质上什么都没有做，而是调用了allocWithZone:方法 -> 实际上真正申请空间，创建对象。
 
 
 init 方法
 1.作用：初始化对象，为对象赋初始值，这个init方法我们叫做构造方法/构造函数
 
 2.重写init方法的规范： - 自定义默认值
 a）必须首先调用父类的init方法，然后将方法的返回值赋值给self
 因为父类的init方法会初始化父类的所有属性，所以必须保证当前对象中的父类属性也同时被初始化。
    self = [super init];
 
 b）调用init方法初始化对象可能会失败，如果初始化失败，返回的值为nil;
 如果初始化成功，就初始化当前对象的属性，最后返回当前对象的值
     if(self != nil)
     {
        self.name = @"...";
     }
     return slef;
 
 3. 自定义init方法
 例：-(void)initWithName:(NSString)name andAge:(int)age;
 
 instancetype
 如果方法的返回值是instancetype 代表方法的返回值是当前这个类的对象。
 
 
 */
#import <Foundation/Foundation.h>
#import "Student.h"
#import "Person.h"
#import "Book.h"


//分组导航标记
#pragma mark - Student

//SEL提取方法的地址
void SelecterReadBook(void);

int main()
{
    
    
    Book *book = [[Book alloc]init];
    Person *person = [[Person alloc]initWithName:@"猫爷爷的酱醋茶" andAge:22 andBook:book];
   // Person *person = [Person personWithName:@"猫爷爷" andAge:22 andBook:book];
    person.book = book;
    book.owern = person;
    
    [person ReadBook];
    
    
    [book release];
    [person release];
    
    
    return 0;
}

void SelecterReadBook(void)
{
    Person *p1 = [Person new];
    
    //效果相当于 [p1 ReadBook];
    SEL s1 = @selector(ReadBook);
    [p1 performSelector:s1];
}
